
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "se_ssl.h"
static void* err_fp = NULL;
#define SE_DEFAULT_CERTS_PATH "/home/web/cert/certs/"
#define SE_DEFAULT_CERT_FILE SE_DEFAULT_CERTS_PATH"ssltest.cert.pem"
#define SE_DEFAULT_KEY_FILE SE_DEFAULT_CERTS_PATH"ssltest.key.pem"
#define SE_DEFAULT_CA_FILE SE_DEFAULT_CERTS_PATH"ca-chain.cert.pem"
void SE_tls_init()
{
    static int i = 0;
    if (i!=0)
        return;
	i++;
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();
    err_fp=stderr;
	return ;
}
void SE_init_tlsctx(SE_TLS_CTX_S *se_tls_ctx, int sd, int isClient)
{
	memset(se_tls_ctx, 0, sizeof(SE_TLS_CTX_S));
	se_tls_ctx->isClient=isClient;
	se_tls_ctx->sock=sd;
	if (!isClient)
	{
		se_tls_ctx->certfile=SE_DEFAULT_CERT_FILE;
		se_tls_ctx->keyfile=SE_DEFAULT_KEY_FILE;
	}
}
void SE_setTlsCtxCert(SE_TLS_CTX_S *se_tls_ctx,char* certfile, char* keyfile)
{
	se_tls_ctx->certfile=certfile;
	se_tls_ctx->keyfile=keyfile;
}
static void ctxloadcert(SSL_CTX* ctx,char* certfile, char* keyfile)
{
	if (!certfile||!keyfile)
	{
		return;
	}
	if (SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM) <= 0) 
	{
		ERR_print_errors_fp(err_fp);
		return;
	}
	if (SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) <= 0)
	{
		ERR_print_errors_fp(err_fp);
		return;
	}

	if (!SSL_CTX_check_private_key(ctx)) 
	{
		fprintf(err_fp,"Private key does not match the certificate public key\n");
		return;
	}
	
	return;
}


static inline SSL_CTX* getTlsCTX(SE_TLS_CTX_S *tls_ctx)
{
    
    SSL_CTX* ctx=NULL;
    SSL_METHOD *meth=NULL;
    if (tls_ctx->isClient)
    {
        meth=(SSL_METHOD *)SSLv23_client_method();
    }
    else
    {
        meth=(SSL_METHOD *)SSLv23_server_method();        
    }
    ctx = SSL_CTX_new (meth);
    if (!ctx)
    {
        return ctx;
    }
    SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
	
	return ctx;
}

static SE_TLS_ERR_CODE se_tls_ctx_check(SE_TLS_CTX_S *ctx)
{
	if (!ctx->isClient)
    {
        if (!ctx->certfile || !ctx->keyfile)
        {
            fprintf(err_fp,"server must have cert file\n");
            return SE_TLS_FAIL;
        }
    }
	if (ctx->isVerifyPeer)
	{
		if (!ctx->cafile)
		{
			fprintf(err_fp,"Need the ca file to verify peer\n");
            return SE_TLS_FAIL;
		}
	}
}
typedef int (*ssl_op_func)(SSL *s);
//under non-blocking socket, SSL_connect and SSL_accept all may return failed
//in the negotiate steps, we need to check the return value.
static SE_TLS_ERR_CODE tls_do_connect(SSL* ssl,ssl_op_func func)
{
	int err;
	int max_loops=10000;
	
	while(max_loops--)
	{
		err=func(ssl);
		if (err<0)
		{
			switch(SSL_get_error(ssl,err))
			{
				case SSL_ERROR_WANT_READ:
				case SSL_ERROR_WANT_WRITE:
				case SSL_ERROR_NONE:
				{
					//sleep(1);
					continue;
				}
				default:
					break;
			}
			fprintf(err_fp,"ssl protocol error code %d\n",SSL_get_error(ssl,err));
			return SE_TLS_TCP_ONLY;
		}
		else if (err >0)
		{
			//success
			return SE_TLS_SUCCESS;
		}
		else
		{
			return SE_TLS_FAIL;
		}
	}
}

 
SE_TLS_ERR_CODE  SE_tls_new(SE_TLS_CTX_S *se_tls_ctx,void** io)
{
	SSL_CTX* ctx;
    SSL*     ssl;
	BIO* ssl_bio;
	BIO *sbio;
    int err;
	ssl_op_func func;

	if (!io)
	{
		fprintf(err_fp,"error parameter\n");
		return SE_TLS_FAIL;
	}
    if (SE_TLS_SUCCESS!=se_tls_ctx_check(se_tls_ctx))
	{
		return SE_TLS_FAIL;
	}

	ctx = getTlsCTX(se_tls_ctx);
	if (!ctx) 
	{
		ERR_print_errors_fp(err_fp);
		return SE_TLS_FAIL;
	}

    ctxloadcert(ctx,se_tls_ctx->certfile,se_tls_ctx->keyfile);

	ssl_bio = BIO_new_ssl (ctx,se_tls_ctx->isClient);
	if (!ssl_bio)
	{
		SSL_CTX_free(ctx);
		ERR_print_errors_fp(err_fp);
		return SE_TLS_FAIL;
	}
	
	BIO_get_ssl( ssl_bio, &ssl);
	sbio = BIO_new_socket(se_tls_ctx->sock, BIO_NOCLOSE);
	if (!sbio)
	{
		BIO_free_all(ssl_bio);
		SSL_CTX_free(ctx);
		ERR_print_errors_fp(err_fp);
		return SE_TLS_FAIL;
	}
	//put the sockect BIO under the ssl BIO
	ssl_bio=BIO_push(ssl_bio,sbio);

	if (se_tls_ctx->isClient)
	{
		func = SSL_connect;
	}
	else
	{
		func = SSL_accept;
	}
	switch(tls_do_connect(ssl,func))
	{
		case SE_TLS_TCP_ONLY:
		{
			fprintf(err_fp,"%s ssl negotiation failed,use tcp\n",se_tls_ctx->isClient?"client":"server");
			//ssl negotiation failed, delete the ssl BIO
			sbio=BIO_pop(ssl_bio);
			BIO_free_all(ssl_bio);
			*io=(void*)sbio;
			return SE_TLS_TCP_ONLY;
		}
		case SE_TLS_SUCCESS:
		{
			*io=(void*)ssl_bio;
			printf("new channel success!\n");
			return SE_TLS_SUCCESS;
		}
		case SE_TLS_FAIL:
		default:
		{
			//the underlying BIO error
			*io=NULL;
			BIO_free_all(ssl_bio);
			SSL_CTX_free(ctx);
			ERR_print_errors_fp(err_fp);
			return SE_TLS_FAIL;
		}
	}
}

SE_TLS_ERR_CODE SE_tcp_new(int sd, void** io)
{
	BIO *sbio;
	if (!io)
	{
		return SE_TLS_FAIL;
	}
	sbio = BIO_new_socket(sd, BIO_NOCLOSE);
	*io=(void*)sbio;
	return SE_TLS_SUCCESS;
}

int SE_tls_read(void* data,void *out, int outl)
{
	int err=0;
	err = BIO_read((BIO*)data,out, outl);
	if (err < 0)
	{
		if (!BIO_should_retry((BIO*)data))
		{
			return -1;
		}
		//application need to retry 
		else
		{
			err=0;
		}
	}
	return err;
}
int SE_tls_write(void* data,void *in, int inl)
{
	int err=0;
	err = BIO_write((BIO*)data,in, inl);
	if (err < 0)
	{
		if (!BIO_should_retry((BIO*)data))
		{
			return -1;
		}
		else
		{
			err=0;
		}
	}
	return err;
}

void SE_tls_delete(void *data)
{
	BIO* ssl_bio;
	SSL*     ssl;
	
	if (!data)
	{
		return;
	}
	ssl_bio=(BIO*)data;
	BIO_get_ssl( ssl_bio, &ssl);
	BIO_free_all(ssl_bio);
	
	return;
}